package com.ineatconseil.yougo.services.rest;

import java.util.List;
import java.util.Map;

import javax.ws.rs.core.MediaType;

import com.ineatconseil.yougo.exceptions.RestServiceException;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.jersey.api.client.filter.LoggingFilter;

/**
 * Use for calls to REST services.
 * 
 * @author johanblo
 * 
 */
public class RestServiceInvoker {

	/**
	 * Rest URL.
	 */
	private String url;

	/**
	 * Jersey client.
	 */
	private static Client client;

	/**
	 * Default constructor.
	 */
	public RestServiceInvoker(Map<String, String> config) {
		super();
		this.url = config.get("URL");
	}

	/**
	 * Init the REST Client.
	 */
	public void initClient(String userName, String password) {

		ClientConfig cc = new DefaultClientConfig();
		cc.getClasses().add(ContextResolver.class);
		client = Client.create(cc);
		client.addFilter(new LoggingFilter());
		client.addFilter(new HTTPBasicAuthFilter(userName, password));

	}

	/**
	 * Implementation of GET method.
	 * 
	 * @param <T>
	 *            The type of object to get.
	 * @param paths
	 *            Paths for complete URL construction.
	 * @param aClass
	 *            The class of objet to get.
	 * @return A valid bean.
	 * @throws RestServiceException
	 */
	@SuppressWarnings("unchecked")
	public <T> T call(List<RestPath> paths, Class<T> aClass) throws RestServiceException {

		return this.call(paths, new GenericType(aClass) {
		});

	}

	/**
	 * Implementation of GET method.
	 * 
	 * @param <T>
	 *            The type of object to get.
	 * @param paths
	 *            Paths for complete URL construction.
	 * @param genericType
	 *            The class of objet to get.
	 * @return A valid bean.
	 * @throws RestServiceException
	 */
	@SuppressWarnings("unchecked")
	public <T> T call(List<RestPath> paths, GenericType<?> genericType) throws RestServiceException {

		try {

			WebResource wr = this.constructPath(paths);

			// Validation du code retour
			ClientResponse cr = wr.get(ClientResponse.class);
			if (Status.OK.equals(cr.getClientResponseStatus())) {
				// Validation de l'entité contenue dans la réponse
				return (T) wr.accept(MediaType.APPLICATION_JSON_TYPE).get(genericType);
			} else {
				throw new RestServiceException(cr.getClientResponseStatus());
			}

		} catch (Exception e) {
			if (e instanceof RestServiceException) {
				RestServiceException re = (RestServiceException) e;
				throw re;
			} else {
				throw new RestServiceException(e);
			}
		}
	}

	/**
	 * Implementation of PUT method.
	 * 
	 * @param <T>
	 *            The type of object to save.
	 * @param aObject
	 *            The object to save.
	 * @param paths
	 *            Paths for complete URL construction.
	 * @param genericType
	 *            The class of objet to get.
	 * @return A valid bean.
	 * @throws RestServiceException
	 */
	public <T> T save(List<RestPath> paths, Object aObject, Class<T> aClass) throws RestServiceException {

		try {

			WebResource wr = this.constructPath(paths);

			// Validation du code retour
			ClientResponse cr = wr.type(MediaType.APPLICATION_JSON_TYPE).post(ClientResponse.class, aObject);

			if (cr.getClientResponseStatus() == Status.CREATED) {

				String locationURI = cr.getLocation().getPath();
				Long createdUserTypeId = Long.parseLong(locationURI.substring(locationURI.lastIndexOf("/") + 1));

				paths.get(paths.size() - 1).setValue(String.valueOf(createdUserTypeId));
				return this.call(paths, aClass);

			} else {
				throw new RestServiceException(cr.getClientResponseStatus());
			}

		} catch (Exception e) {
			if (e instanceof RestServiceException) {
				RestServiceException re = (RestServiceException) e;
				throw re;
			} else {
				throw new RestServiceException(e);
			}
		}

	}

	/**
	 * Implementation of UPDATE method.
	 * 
	 * @param <T>
	 *            The type of object to save.
	 * @param aObject
	 *            The object to save.
	 * @param paths
	 *            Paths for complete URL construction.
	 * @param genericType
	 *            The class of objet to get.
	 * @return A valid bean.
	 * @throws RestServiceException
	 */
	public <T> T update(List<RestPath> paths, Object aObject, Class<T> aClass) throws RestServiceException {

		try {

			WebResource wr = this.constructPath(paths);

			// Validation du code retour
			ClientResponse cr = null;

			if (aObject != null && aClass != null) {
				cr = wr.type(MediaType.APPLICATION_JSON_TYPE).put(ClientResponse.class, aObject);
			} else {
				cr = wr.put(ClientResponse.class);
			}

			if (cr.getClientResponseStatus() == Status.OK) {
				return this.call(paths, aClass);
			} else {
				throw new RestServiceException(cr.getClientResponseStatus());
			}

		} catch (Exception e) {
			if (e instanceof RestServiceException) {
				RestServiceException re = (RestServiceException) e;
				throw re;
			} else {
				throw new RestServiceException(e);
			}
		}

	}

	/**
	 * Implementation of DELETE method.
	 * 
	 * @param paths
	 *            Paths for complete URL construction.
	 * @throws RestServiceException
	 */
	public void delete(List<RestPath> paths) throws RestServiceException {

		try {

			WebResource wr = this.constructPath(paths);

			// Validation du code retour
			ClientResponse cr = wr.delete(ClientResponse.class);
			if (!Status.OK.equals(cr.getClientResponseStatus())) {
				throw new RestServiceException(cr.getClientResponseStatus());
			}

		} catch (Exception e) {
			if (e instanceof RestServiceException) {
				RestServiceException re = (RestServiceException) e;
				throw re;
			} else {
				throw new RestServiceException(e);
			}
		}

	}

	/**
	 * Construction of the complete path of REST service to call.
	 * 
	 * @param paths
	 *            List of keys path<->value.
	 * @return a WebResource.
	 */
	private WebResource constructPath(List<RestPath> paths) {

		WebResource wr = null;
		int cpt = 0;
		for (RestPath restPath : paths) {
			if (cpt == 0) {
				wr = getResource(restPath.getPath());
			} else {
				wr = wr.path(restPath.getPath());
			}
			if (restPath.getValue() != null && !"".equals(restPath.getValue())) {
				wr = wr.path(restPath.getValue());
			}
			cpt++;
		}

		return wr;
	}

	/**
	 * Get the complete path.
	 * 
	 * @param relativeUrl
	 *            A relative path to reach.
	 * @return The complete path.
	 */
	private WebResource getResource(String relativeUrl) {
		String realUrl = this.url + relativeUrl;
		return client.resource(realUrl);
	}

}
